﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using DocumentFormat.OpenXml;
using System.Xml.XPath;
using System.Xml;
using System.Net;
using System.Xml.Xsl;
using System.Threading;
using System.IO;
using System.Reflection;
using System.Globalization;
using System.Drawing.Imaging;
using System.Drawing;
using System.Diagnostics;
using DocumentFormat.OpenXml.Drawing.Wordprocessing;

namespace fleXdoc.Api
{
    internal class fleXdocTemplateProcessor : BaseTemplateProcessor
    {
        #region Constants
        //private const string NS_FLEXDOC = "http://www.infosupport.nl/xmlquery";
        private const string NS_FLEXDOC = "urn:fleXdoc";
        private const string ELEMENT_IMPORTTEMPLATE = "ImportTemplate";
        private const string ELEMENT_INCLUDETEMPLATE = "IncludeTemplate";
        private const string ELEMENT_IMGOF = "ImgOf";
        private const string ELEMENT_USECONTEXT = "UseContext";
        #endregion

        #region Constructor(s) and destructor
        public fleXdocTemplateProcessor()
            : base()
        {
        }

        ~fleXdocTemplateProcessor()
        {
            Dispose(false);
        }
        #endregion

        #region BaseTemplateProcessor overrides
        protected override void PreProcessCustomXmlElement(WordprocessingDocument doc, OpenXmlPart part, OpenXmlCompositeElement element, string namespaceUri, string elementName, CustomXmlProperties properties, BuildOptions options)
        {
            if (namespaceUri == NS_FLEXDOC)
            {
                switch (elementName)
                {
                    case ELEMENT_IMPORTTEMPLATE:
                        PreProcessTag_ImportTemplate(doc, part, element, properties, options);
                        break;
                    case ELEMENT_INCLUDETEMPLATE:
                        PreProcessTag_IncludeTemplate(doc, part, element, properties, options);
                        break;
                }
            }
        }

        protected override void ProcessOpenXmlPart(WordprocessingDocument doc, OpenXmlPart part, BuildOptions options)
        {
            XmlResolver resolver = (XmlResolver)new XmlUrlResolver();
            resolver.Credentials = CredentialCache.DefaultCredentials; // Credentials for current request

            XsltArgumentList xsltArgs = new XsltArgumentList();
            xsltArgs.AddExtensionObject("urn:flexdoc.api.xsltextensions", new XsltExtensions(options.RenderCulture));
            if (options.Data == null)
            {
                options.Data = new XmlDocument().CreateNavigator(); // Empty document
            }
            xsltArgs.AddParam("data", string.Empty, options.Data);
            xsltArgs.AddParam("dataNsPrefix", string.Empty, options.NamespacePrefix);
            xsltArgs.AddParam("dataNs", string.Empty, options.NamespaceUri);

            MemoryStream outputStream = null;
            XmlReader templateReader = null;

            try
            {
                // Open the part-XML (template)
                using (templateReader = XmlReader.Create(part.GetStream(FileMode.Open, FileAccess.Read)))
                {
                    // Open the outputstream
                    outputStream = new MemoryStream();
                    XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
                    XmlWriter xmlWriter = XmlWriter.Create(outputStream, xmlWriterSettings);

                    // Do the magic
                    Transform.Transform(templateReader, xsltArgs, xmlWriter, resolver);
                    xmlWriter.Flush();
                }

                // Write the outputstream back to the part
                outputStream.Seek(0, SeekOrigin.Begin);
                part.FeedData(outputStream);
                part.RootElement.Reload(); // Update the DOM
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null)
                {
                    Exception exToLog = ex;
                    while (exToLog.InnerException != null)
                    {
                        exToLog = exToLog.InnerException;
                    }
                    throw exToLog;
                }
                else
                {
                    throw; // org
                }
            }
            finally
            {
                outputStream.Dispose();
            }
        }

        protected override void PostProcessCustomXmlElement(WordprocessingDocument doc, OpenXmlPart part, OpenXmlCompositeElement element, string namespaceUri, string elementName, CustomXmlProperties properties, BuildOptions options)
        {
            if (namespaceUri == NS_FLEXDOC)
            {
                switch (elementName)
                {
                    case ELEMENT_IMGOF:
                        PostProcessTag_ImgOf(doc, part, element, properties, options);
                        break;
                }
            }
        }

        protected override void PostProcessOpenXmlPart(WordprocessingDocument doc, OpenXmlPart part, BuildOptions options)
        {
            // Replace enters in the text (\n) with <w:br />
            FixCrLf(part);

            // Renumber the bookmarks
            MakeUnique_Bookmarks(part);

            // Renumber the images (inline.docprs)
            MakeUnique_DocProperties(part);

            // Renumber the drawing properties of the images
            MakeUnique_Pictures_NonVisualDrawingProperties(part);
        }

        #endregion

        #region Private methods

        private void PostProcessTag_ImgOf(WordprocessingDocument doc, OpenXmlPart part, OpenXmlCompositeElement element, CustomXmlProperties properties, BuildOptions options)
        {
            MemoryStream memImg = new MemoryStream();

            // See if data already contains something: this means that path was specified an matched with a node
            string base64Data = (from p in properties.Elements<CustomXmlAttribute>()
                                 where p.Name == "data"
                                 select p.Val.Value).FirstOrDefault();

            if (!String.IsNullOrEmpty(base64Data))
            {
                // Load the image from data base64-encoded datastream
                byte[] imgBytes = System.Convert.FromBase64String(base64Data);
                memImg.Write(imgBytes, 0, imgBytes.Length);
                memImg.Position = 0;
            }
            else // No path specified or it matched no node, now try 'uri'
            {
                string uri = (from p in properties.Elements<CustomXmlAttribute>()
                              where p.Name == "uri"
                              select p.Val.Value).FirstOrDefault();
                if (!String.IsNullOrEmpty(uri))
                {
                    // Resolve the uri
                    uri = OnResourceUriResolve(uri);

                    // Just overwrite the original memImg (no use to copy it)
                    memImg = IOHelper.GetUri(uri, FileMode.Open, FileAccess.Read, FileShare.Read);
                }
            }

            if (memImg.Length > 0) // Data was found
            {
                Image img = Image.FromStream(memImg);
                memImg.Position = 0;

                // Add an imagepart to the document and feed it the image
                ImagePartType imgType = GetImagePartTypeFromImageFormat(img.RawFormat);
                ImagePart imgPart = AddImagePart(part, imgType);
                imgPart.FeedData(memImg);

                Run run = new Run();
                int widthEmu = 0;
                int heightEmu = 0;
                CalculateImageEmus(img.Width, img.HorizontalResolution, img.Height, img.VerticalResolution, out widthEmu, out heightEmu);
                run.AppendChild(CreateImageXml(part, part.GetIdOfPart(imgPart), "Image1", widthEmu, heightEmu));
                element.InsertAfterSelf(run);
            }
            element.Remove();

            //part.RootElement.Save();
            MainDocumentPart mainPart = part as MainDocumentPart;
            if (mainPart != null)
            {
                mainPart.Document.Save();
            }
            else
            {
                part.RootElement.Save();
            }
        }

        private void PreProcessTag_ImportTemplate(WordprocessingDocument doc, OpenXmlPart part, OpenXmlCompositeElement element, CustomXmlProperties properties, BuildOptions options)
        {
            // If the ImportTemplate-tag is not a direct child of the part-body, make it a child right after it's parent (not 100% save yet... (TODO))
            bool parentIsParagraph = (element.Parent is Paragraph);
            if (parentIsParagraph)
            {
                Debug.WriteLine("FlexDocTemplateProcessor:PreProcessTag_ImportTemplate: Warning: ImportTemplate cannot be contained within a paragraph and has therefore been moved right after the paragraph");
                OpenXmlCompositeElement newElement = (OpenXmlCompositeElement)element.CloneNode(true);
                element.Parent.InsertAfterSelf<OpenXmlCompositeElement>(newElement);
                element.Remove();
                element = newElement;
                part.RootElement.Save();
            }

            if (properties != null)
            {
                string templatePath = (from p in properties.Elements<CustomXmlAttribute>()
                                       where p.Name == "uri"
                                       select p.Val.Value).FirstOrDefault();

                if (!String.IsNullOrEmpty(templatePath))
                {
                    // Resolve the uri
                    templatePath = OnResourceUriResolve(templatePath);

                    string absoluteDataPath = (from p in properties.Elements<CustomXmlAttribute>()
                                               where p.Name == "absolutePath"
                                               select p.Val.Value).FirstOrDefault();

                    if (String.IsNullOrEmpty(absoluteDataPath))
                    {
                        absoluteDataPath = "."; // current
                    }

                    // Create the buildoptions for the template
                    BuildOptions templateOptions = new BuildOptions();
                    templateOptions.NamespacePrefix = options.NamespacePrefix;
                    templateOptions.NamespaceUri = options.NamespaceUri;
                    templateOptions.RenderCulture = options.RenderCulture;
                    templateOptions.ValidatePackage = options.ValidatePackage;

                    // Determine xpath-navigator for template
                    XmlNamespaceManager nsMgr = new XmlNamespaceManager(options.Data.NameTable);
                    nsMgr.AddNamespace(options.NamespacePrefix, options.NamespaceUri);
                    templateOptions.Data = options.Data.SelectSingleNode(absoluteDataPath, nsMgr);
                    if (templateOptions.Data == null)
                    {
                        Trace.TraceWarning("ImportTemplate ({0}): absoluteDataPath ({1})resulted in an empty node-set", templatePath, absoluteDataPath);
                    }

                    // Process the template
                    MemoryStream templateStream = IOHelper.GetUri(templatePath, FileMode.Open, FileAccess.Read, FileShare.Read);
                    WordprocessingDocument docTemplate = WordprocessingDocument.Open(templateStream, true);
                    
                    using (fleXdocTemplateProcessor fdProcessor = new fleXdocTemplateProcessor())
                    {
                        fdProcessor.ResourceUriResolve += delegate(object sender, ResolveResourceUriEventArgs args)
                        {
                            return OnResourceUriResolve(args.ResourceUri);
                        };
                        fdProcessor.ProcessWordprocessingDocument(docTemplate, templateOptions);
                    }

                    // Import external parts for the maindocumentpart of the template
                    ImportDrawingParts(docTemplate.MainDocumentPart, doc, part);
                    ImportHyperlinkParts(docTemplate.MainDocumentPart, doc, part);

                    // Remove any child sectionproperties
                    List<SectionProperties> sectPrList = docTemplate.MainDocumentPart.Document.Body.Descendants<SectionProperties>().ToList();
                    for (int index = 0; index < sectPrList.Count; index++)
                    {
                        sectPrList[index].Remove();
                    }
                    docTemplate.MainDocumentPart.RootElement.Save();

                    // Import all children of the body of the maindocumentpart of the template to be imported
                    foreach (OpenXmlElement templateChild in docTemplate.MainDocumentPart.Document.Body.ChildElements.Reverse())
                    {
                        element.InsertAfterSelf(templateChild.CloneNode(true));
                    }
                }
            }
            element.Remove();

            part.RootElement.Save();
        }

        private static void FixCrLf(OpenXmlPart part)
        {
            foreach (Text textNode in part.RootElement.Descendants<Text>())
            {
                if (textNode.Text != null && textNode.Text.Contains("\n"))
                {
                    string[] textParts = textNode.Text.Split(new string[] { "\n" }, StringSplitOptions.None);
                    if (textParts.Length > 0)
                    {
                        textNode.Text = textParts[0];
                        Text previousTextNode = textNode;
                        for (int index = 1; index < textParts.Length; index++)
                        {
                            Break lineBreak = new Break();
                            lineBreak = previousTextNode.InsertAfterSelf(lineBreak);
                            Text newTextNode = (Text)previousTextNode.Clone();
                            newTextNode.Text = textParts[index];
                            lineBreak.InsertAfterSelf(newTextNode);
                            previousTextNode = newTextNode;
                        }
                    }
                    else // The textnode only contained a single break
                    {
                        textNode.InsertBeforeSelf(new Break());
                        textNode.Remove();
                    }
                }
            }
            part.RootElement.Save();
        }

        private static void MakeUnique_Pictures_NonVisualDrawingProperties(OpenXmlPart part)
        {
            uint lastUInt32ID = 0;
            foreach (DocumentFormat.OpenXml.Drawing.Pictures.NonVisualDrawingProperties drawingProperties in part.RootElement.Descendants<DocumentFormat.OpenXml.Drawing.Pictures.NonVisualDrawingProperties>())
            {
                drawingProperties.Id = new UInt32Value(lastUInt32ID);
                lastUInt32ID++;
            }
            part.RootElement.Save();
        }

        private static void MakeUnique_DocProperties(OpenXmlPart part)
        {
            uint lastUInt32ID = 0;
            foreach (DocProperties docProps in part.RootElement.Descendants<DocProperties>())
            {
                docProps.Id = new UInt32Value(lastUInt32ID);
                lastUInt32ID++;
            }
            part.RootElement.Save();
        }

        private static void MakeUnique_Bookmarks(OpenXmlPart part)
        {
            int lastInt32ID = 0;
            foreach (BookmarkStart bmStart in part.RootElement.Descendants<BookmarkStart>())
            {
                // Find the bookmark-end (can be a following-sibling, but also a child of a following-sibling)
                BookmarkEnd bmEnd = null;
                foreach (OpenXmlElement element in bmStart.ElementsAfter())
                {
                    bmEnd = element as BookmarkEnd;
                    if (bmEnd == null)
                    {
                        // Check it's child elements
                        bmEnd = element.Descendants<BookmarkEnd>().FirstOrDefault();
                    }
                    if (bmEnd != null)
                    {
                        break; // Got it!
                    }
                }

                if (bmEnd != null) // Very odd if we haven't found it yet...
                {
                    string bmID = lastInt32ID.ToString(CultureInfo.InvariantCulture);
                    bmStart.Id = bmID;
                    bmEnd.Id = bmID;
                    lastInt32ID++;
                }
            }
            part.RootElement.Save();
        }

        private void PreProcessTag_IncludeTemplate(WordprocessingDocument doc, OpenXmlPart part, OpenXmlCompositeElement element, CustomXmlProperties properties, BuildOptions options)
        {
            string templatePath = (from p in properties.Elements<CustomXmlAttribute>()
                                   where p.Name == "uri"
                                   select p.Val.Value).FirstOrDefault();

            if (!String.IsNullOrEmpty(templatePath))
            {
                // Resolve the uri
                templatePath = OnResourceUriResolve(templatePath);

                string absoluteDataPath = (from p in properties.Elements<CustomXmlAttribute>()
                                           where p.Name == "absolutePath"
                                           select p.Val.Value).FirstOrDefault();
                
                if (String.IsNullOrEmpty(absoluteDataPath))
                {
                    absoluteDataPath = "."; // current
                }

                // Read the template into memory and do further processing from there (we don't want to modify the template itself)
                MemoryStream templateStream = IOHelper.GetUri(templatePath, FileMode.Open, FileAccess.Read, FileShare.Read);

                // Create the buildoptions for the template
                BuildOptions templateOptions = new BuildOptions();
                templateOptions.NamespacePrefix = options.NamespacePrefix;
                templateOptions.NamespaceUri = options.NamespaceUri;
                templateOptions.RenderCulture = options.RenderCulture;
                templateOptions.ValidatePackage = options.ValidatePackage;

                // Determine xpath-navigator for template
                XmlNamespaceManager nsMgr = new XmlNamespaceManager(options.Data.NameTable);
                nsMgr.AddNamespace(options.NamespacePrefix, options.NamespaceUri);
                templateOptions.Data = options.Data.SelectSingleNode(absoluteDataPath, nsMgr);
                if (templateOptions.Data == null)
                {
                    Trace.TraceWarning("IncludeTemplate ({0}): absoluteDataPath ({1})resulted in an empty node-set", templatePath, absoluteDataPath);
                }

                // Proces the template (updates the memorystream)
                WordprocessingDocument docTemplate = WordprocessingDocument.Open(templateStream, true);
                using (fleXdocTemplateProcessor fdProcessor = new fleXdocTemplateProcessor())
                {
                    fdProcessor.ResourceUriResolve += delegate(object sender, ResolveResourceUriEventArgs args)
                    {
                        return OnResourceUriResolve(args.ResourceUri);
                    };
                    fdProcessor.ProcessWordprocessingDocument(docTemplate, templateOptions);
                }
                docTemplate.Close();
                templateStream.Position = 0;

                // Insert contents of template file
                element.InsertAfterSelf(LoadChunk(part, templateStream));
            }
            element.Remove();

            part.RootElement.Save();
        }

        private static void ImportDrawingParts(OpenXmlPart partFrom, WordprocessingDocument docTo, OpenXmlPart partTo)
        {
            Dictionary<string, string> relIdMapping = new Dictionary<string, string>();

            IEnumerable<Drawing> drawings = partFrom.RootElement.Descendants<Drawing>();
            foreach (Drawing drawing in drawings)
            {
                if (drawing.Inline != null
                    && drawing.Inline.Graphic != null)
                {
                    DocumentFormat.OpenXml.Drawing.Blip blip = drawing.Inline.Graphic.Descendants<DocumentFormat.OpenXml.Drawing.Blip>().FirstOrDefault();
                    if (blip != null)
                    {
                        if (!String.IsNullOrEmpty(blip.Embed)) // Relates to imagepart
                        {
                            string newId = null;
                            if (!relIdMapping.ContainsKey(blip.Embed)) // Hasn't been imported yet
                            {
                                OpenXmlPart relation = partFrom.GetPartById(blip.Embed);
                                newId = "Img" + Guid.NewGuid().ToString("D", CultureInfo.InvariantCulture);
                                partTo.AddPart(relation, newId);
                                relIdMapping.Add(blip.Embed, newId); // Add mapping
                            }
                            else
                            {
                                newId = relIdMapping[blip.Embed.Value];
                            }
                            blip.Embed = newId;
                        }
                    }
                }
            }
            partFrom.RootElement.Save();
        }

        private static void ImportHyperlinkParts(OpenXmlPart partFrom, WordprocessingDocument docTo, OpenXmlPart partTo)
        {
            IEnumerable<Hyperlink> hyperlinks = partFrom.RootElement.Descendants<Hyperlink>();
            foreach (Hyperlink link in hyperlinks)
            {
                if (link.Id.HasValue)
                {
                    HyperlinkRelationship linkRelFrom = partFrom.HyperlinkRelationships.Where(l => l.Id == link.Id.Value).FirstOrDefault();
                    if (linkRelFrom != null)
                    {
                        HyperlinkRelationship linkRelTo = partTo.AddHyperlinkRelationship(linkRelFrom.Uri, linkRelFrom.IsExternal);
                        link.Id.Value = linkRelTo.Id;
                    }
                }
            }
            partFrom.RootElement.Save();
            partTo.RootElement.Save();
        }

        private static ImagePartType GetImagePartTypeFromImageFormat(System.Drawing.Imaging.ImageFormat imageFormat)
        {
            if (imageFormat.Equals(ImageFormat.Bmp))
            {
                return ImagePartType.Bmp;
            }
            else if (imageFormat.Equals(ImageFormat.Emf))
            {
                return ImagePartType.Emf;
            }
            else if (imageFormat.Equals(ImageFormat.Gif))
            {
                return ImagePartType.Gif;
            }
            else if (imageFormat.Equals(ImageFormat.Icon))
            {
                return ImagePartType.Icon;
            }
            else if (imageFormat.Equals(ImageFormat.Jpeg))
            {
                return ImagePartType.Jpeg;
            }
            else if (imageFormat.Equals(ImageFormat.MemoryBmp))
            {
                return ImagePartType.Bmp;
            }
            else if (imageFormat.Equals(ImageFormat.Png))
            {
                return ImagePartType.Png;
            }
            else if (imageFormat.Equals(ImageFormat.Tiff))
            {
                return ImagePartType.Tiff;
            }
            else if (imageFormat.Equals(ImageFormat.Wmf))
            {
                return ImagePartType.Wmf;
            }
            else
            {
                throw new NotSupportedException("Image format not supported: " + imageFormat.ToString());
            }
        }

        private static void CalculateImageEmus(int width, float horizontalResolution, int height, float verticalResolution, out int widthInEmu, out int heightInEmu)
        {
            float widthInInches = (float)width / horizontalResolution;
            float heightInInches = (float)height / verticalResolution;

            widthInEmu = (int)(widthInInches * 914400);
            heightInEmu = (int)(heightInInches * 914400);
        }

        private static Drawing CreateImageXml(OpenXmlPart part, string relId, string imageName, int width, int height)
        {
            Drawing wpDrawing = new Drawing();

            Inline wpInline = new Inline();

            wpInline.Extent = new Extent() { Cx = width, Cy = height };

            // Id must be unique, but this is automatically fixed by PostProcessOpenXmlPart!
            wpInline.DocProperties = new DocProperties() { Name = imageName, Id = 0 };


            DocumentFormat.OpenXml.Drawing.Pictures.Picture pic = new DocumentFormat.OpenXml.Drawing.Pictures.Picture();
            pic.NonVisualPictureProperties = new DocumentFormat.OpenXml.Drawing.Pictures.NonVisualPictureProperties();
            pic.NonVisualPictureProperties.NonVisualDrawingProperties = new DocumentFormat.OpenXml.Drawing.Pictures.NonVisualDrawingProperties() { Id = wpInline.DocProperties.Id, Name = imageName };
            pic.NonVisualPictureProperties.NonVisualPictureDrawingProperties = new DocumentFormat.OpenXml.Drawing.Pictures.NonVisualPictureDrawingProperties();

            pic.BlipFill = new DocumentFormat.OpenXml.Drawing.Pictures.BlipFill();
            DocumentFormat.OpenXml.Drawing.Blip blip = new DocumentFormat.OpenXml.Drawing.Blip() { Embed = new StringValue(relId) };
            pic.BlipFill.Blip = blip;
            DocumentFormat.OpenXml.Drawing.Stretch stretch = new DocumentFormat.OpenXml.Drawing.Stretch();
            stretch.FillRectangle = new DocumentFormat.OpenXml.Drawing.FillRectangle();
            pic.BlipFill.AppendChild(stretch);
            
            DocumentFormat.OpenXml.Drawing.Transform2D xfrm2D = new DocumentFormat.OpenXml.Drawing.Transform2D();
            xfrm2D.Offset = new DocumentFormat.OpenXml.Drawing.Offset() { X = 0, Y = 0 };
            xfrm2D.Extents = new DocumentFormat.OpenXml.Drawing.Extents() { Cx = width, Cy = height };
            pic.ShapeProperties = new DocumentFormat.OpenXml.Drawing.Pictures.ShapeProperties() { Transform2D = xfrm2D };
            pic.ShapeProperties.AppendChild(new DocumentFormat.OpenXml.Drawing.PresetGeometry() { Preset = DocumentFormat.OpenXml.Drawing.ShapeTypeValues.Rectangle });

            DocumentFormat.OpenXml.Drawing.Graphic aGraphic = new DocumentFormat.OpenXml.Drawing.Graphic();
            aGraphic.GraphicData = new DocumentFormat.OpenXml.Drawing.GraphicData() { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" };
            aGraphic.GraphicData.AppendChild(pic);
            wpInline.AppendChild(aGraphic);

            wpDrawing.Inline = wpInline;
            return wpDrawing;
        }

        private ImagePart AddImagePart(OpenXmlPart part, ImagePartType imgType)
        {
            MainDocumentPart mainDocPart = part as MainDocumentPart;
            if (mainDocPart != null)
            {
                return mainDocPart.AddImagePart(imgType);
            }

            HeaderPart headerPart = part as HeaderPart;
            if (headerPart != null)
            {
                return headerPart.AddImagePart(imgType);
            }

            FooterPart footerPart = part as FooterPart;
            if (footerPart != null)
            {
                return footerPart.AddImagePart(imgType);
            }

            throw new InvalidOperationException("Unsupported part, ImagePart could not be added.");
        }

        private static AltChunk LoadChunk(OpenXmlPart part, Stream chunkData)
        {
            string altChunkId = "AltChunk" + Guid.NewGuid().ToString("D", CultureInfo.InvariantCulture);
            AlternativeFormatImportPart chunk = null;

            MainDocumentPart mainDocPart = part as MainDocumentPart;
            HeaderPart headerPart = null;
            FooterPart footerPart = null;
            if (mainDocPart == null)
            {
                headerPart = part as HeaderPart;
                if (headerPart == null)
                {
                    footerPart = part as FooterPart;
                    if (footerPart == null)
                    {
                        throw new ArgumentException("Unsupported OpenXmlPart-type: must be MainDocumentPart, HeaderPart or FooterPart", "part");
                    }
                    else
                    {
                        chunk = footerPart.AddAlternativeFormatImportPart(AlternativeFormatImportPartType.WordprocessingML, altChunkId);
                    }
                }
                else
                {
                    chunk = headerPart.AddAlternativeFormatImportPart(AlternativeFormatImportPartType.WordprocessingML, altChunkId);
                }
            }
            else
            {
                chunk = mainDocPart.AddAlternativeFormatImportPart(AlternativeFormatImportPartType.WordprocessingML, altChunkId);
            }

            chunk.FeedData(chunkData);
            AltChunk altChunk = new AltChunk();
            altChunk.Id = altChunkId;
            return altChunk;
        }
        #endregion

        #region Cached XslCompiledTransform
        private static XslCompiledTransform _cachedTransform = null;
        private static object _cachedTransformLock = new object();

        private XslCompiledTransform Transform
        {
            get
            {
                if (_cachedTransform == null)
                {
                    lock (_cachedTransformLock)
                    {
                        if (_cachedTransform == null) // Check again, since it may have been set while we were waiting for the lock
                        {
                            lock (_cachedTransformLock)
                            {
                                XPathDocument xsltDoc = new XPathDocument(Assembly.GetExecutingAssembly().GetManifestResourceStream("fleXdoc.Api.XmlQueryProcessor.xslt"));
#if DEBUG
                                XslCompiledTransform transform = new XslCompiledTransform(true);
#else
                                XslCompiledTransform transform = new XslCompiledTransform(false);
#endif
                                XsltSettings settings = new XsltSettings(true, true);
                                transform.Load(xsltDoc, settings, null); // No resolver specified on the transform itself, since the resolver needs credentials which may differ for each call.
                                _cachedTransform = transform;
                            }
                        }
                    }
                }
                return _cachedTransform;
            }
        }
        #endregion
    }
}
